<template>
	<view :class="maxCount>1?'uploadFile uploadFile_max':'uploadFile'">
		<uv-upload ref="uvUpload" :uploadText="uploadText" :id="'my-drag-files'+name" :name="String(name)"
			:accept="accept" :capture="capture" class="uv_upload" :fileList="fileList" :multiple="maxCount>1?true:false"
			:maxCount="maxCount" @beforeRead="beforeRead" @afterRead="afterRead" @delete="deletePic"
			:previewFullImage="true" :width="viewWidth" :height="viewHeight" :imageMode="mode">
		</uv-upload>
		<helang-compress ref="helangCompress"></helang-compress>
	</view>
</template>

<script>
	import config from '@/common/config'
	import CryptoJS from 'crypto-js';
	import {
		upload,
		uploadFileName,
		postName,
		getName
	} from '@/common/api.js';
	import helangCompress from '@/components/compress/compress.vue';
	import FileHashCalculator from '@/util/fileHash.js';
	import Md5File from '@/util/md5File.js';
	import {
		uploadFileToOSS
	} from '@/util/oss.js';

	// 常量定义
	const CHUNK_SIZE = 5 * 1024 * 1024; // 5MB
	const MAX_RETRIES = 3;
	const CONCURRENT_UPLOADS = 5;

	export default {
		components: {
			helangCompress
		},
		props: {
			fileList: {
				type: Array,
				default: () => []
			},
			accept: {
				type: String,
				default: 'all'
			},
			parentId: {
				type: String,
				default: null
			},
			mode: {
				type: String,
				default: 'widthFix'
			},
			uploadText: {
				type: String,
				default: ''
			},
			maxSize: {
				type: [Number, String],
				default: 3 * 1024 * 1024
			},
			limit: {
				type: [Number, String],
				default: 1 * 1024 * 1024 * 1024
			},
			minWidth: {
				type: [Number, String],
				default: 1
			},
			maxCount: {
				type: [Number, String],
				default: '1'
			},
			name: {
				type: [Number, String],
				default: '1'
			},
			height: {
				type: [Number, String],
				default: 0
			},
			width: {
				type: [Number, String],
				default: 0
			},
		},
		data() {
			return {
				viewWidth: this.height,
				viewHeight: this.width,
				obj: {},
				capture: ['album', 'camera'],
				type: '',
				resdata: [],
				afterIndex: 0,
			}
		},
		computed: {
			allowType() {
				const image = ["png", "jpeg", "jpg", "gif", "webp", "svg", 'bmp'];
				const video = ['mp4', 'webm', 'ogg', 'mov', 'avi', 'mkv'];

				switch (this.accept) {
					case 'image':
						return image;
					case 'video':
						return video;
					case 'media':
						return ['mp3', 'wav', ...image, ...video];
					case 'file':
						return ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt'];
					default:
						return this.accept.includes(',') ? this.accept.split(',') : undefined;
				}
			},
		},
		watch: {
			obj(newVal) {
				this.$emit('obj', newVal);
			},
			resdata(newVal) {
				this.$emit('resdata', newVal);
			},
		},
		mounted() {
			if (!this.height && !this.width) {
				this.getViewSize(true);
			}
			this.addEventListeners();
		},
		beforeDestroy() {
			this.removeEventListeners();
		},
		beforeUnmount() {
			this.removeEventListeners();
		},
		methods: {
			beforeRead(file, lists, name) {
				console.log(file, lists, name);
			},
			// 事件监听器管理
			addEventListeners() {
				const elem = document.getElementById(`my-drag-files${this.name}`);
				if (!elem) return;

				const events = [
					['drop', this.ondrop],
					['dragleave', this.ondropprevent],
					['dragenter', this.ondropprevent],
					['dragover', this.ondropprevent]
				];

				events.forEach(([event, handler]) => {
					elem.addEventListener(event, handler, false);
				});

				this.elem = elem;
			},

			removeEventListeners() {
				const {
					elem
				} = this;
				if (!elem) return;

				const events = [
					['drop', this.ondrop],
					['dragleave', this.ondropprevent],
					['dragenter', this.ondropprevent],
					['dragover', this.ondropprevent]
				];

				events.forEach(([event, handler]) => {
					elem.removeEventListener(event, handler);
				});
			},

			handleClick() {
				this.$refs.uvUpload.chooseFile();
			},

			ondrop(event) {
				const files = Array.from(event.dataTransfer.files);
				const list = files.map(file => ({
					name: file.name,
					size: file.size,
					type: file.type,
					url: this.getTempFilePath(file),
				}));

				this.afterRead({
					file: list,
					name: String(this.name),
					index: this.fileList.length
				});

				this.ondropprevent(event);
			},

			ondropprevent(event) {
				event.stopPropagation();
				event.preventDefault();
			},

			toast(e) {
				this.$emit('toast', e);
			},

			getTempFilePath(file) {
				if (window.URL && window.URL.createObjectURL) {
					return window.URL.createObjectURL(file);
				} else if (window.webkitURL && window.webkitURL.createObjectURL) {
					return window.webkitURL.createObjectURL(file);
				} else if (window.createObjectURL) {
					return window.createObjectURL(file);
				} else {
					console.error('getTempFilePath has error');
					return '';
				}
			},

			getViewSize(e) {
				const query = uni.createSelectorQuery().in(this);
				query.select('.uv_upload').boundingClientRect(data => {
					if (data) {
						this.obj = data;
						if (e) {
							this.viewWidth = data.width;
							this.viewHeight = parseInt(this.viewWidth / 1.9);
							this.obj.height = this.viewHeight;
						}
					}
				}).exec();
			},

			// 删除图片
			deletePic(e) {
				this.resdata.splice(e.index, 1);
				this.fileList.splice(e.index, 1);
			},

			deletes(url) {
				const index = this.fileList.findIndex(item => item.url === url);
				if (index !== -1) {
					this.fileList.splice(index, 1);
				}
			},

			// 新增图片 - 主要优化逻辑
			async afterRead(event) {
				this.$emit('focus');

				const lists = [].concat(event.file);
				let fileListLen = this.fileList.length;

				// 添加文件到列表
				lists.forEach(item => {
					this.fileList.push({
						...item,
						status: 'uploading',
						message: '上传中'
					});
				});

				// 逐个处理文件上传
				for (let i = 0; i < lists.length; i++) {
					let file = lists[i];
					this.updateUploadProgress(i, {
						progress: 1
					});

					// 检查文件数量限制
					if (this.fileList.length > this.maxCount) {
						this.deletes(file.url);
						continue;
					}

					// 验证文件类型和大小
					if (!this.validateFile(file)) {
						continue;
					}

					try {
						// 并行处理文件信息和哈希计算
						const [imageInfo, mimeType, fileHash] = await Promise.all([
							this.getImageInfo(file.url),
							this.getFileMimeType(file),
							this.calculateFileHash(file)
						]);

						file.fileHash = fileHash;
						file.mimeType = mimeType;

						// 图片尺寸验证
						if (imageInfo && Number(imageInfo.width) < Number(this.minWidth)) {
							this.toast({
								message: `图片尺寸不小于${this.minWidth}px`
							});
							this.deletes(file.url);
							continue;
						}

						// 上传文件
						const data = await this.uploadFilePromise(file, i);
						if (data?.url) {
							this.updateFileList(fileListLen, data);
							fileListLen++;
						}
					} catch (error) {
						console.error(`文件 ${file.name} 处理失败:`, error);
						this.deletes(file.url);
					}
					this.updateUploadProgress(i, {
						progress: 100
					});
				}

			},

			// 文件验证
			validateFile(file) {
				// 类型验证
				const typeExt = file.type.split('/')[0] || file.type;
				const ext = file.name.split('.').pop().toLowerCase();

				if (this.accept !== "all" && this.allowType?.[0]) {
					if (this.accept !== typeExt || !this.allowType.some(ele => ele.includes(ext))) {
						this.toast({
							message: `请上传 ${this.allowType.join("、").replaceAll('.', '')} 格式文件`
						});
						return false;
					}
				}

				// 大小验证
				const maxSize = this.accept === 'image' ? this.maxSize : this.limit;
				if (Number(file.size) > Number(maxSize)) {
					const maxSizeMB = maxSize / 1024 / 1024;
					this.toast({
						message: `文件大小超过 ${maxSizeMB} M`
					});
					return false;
				}

				return true;
			},

			// 获取图片信息
			getImageInfo(url) {
				return new Promise((resolve) => {
					const imageTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'image/webp',
						'image/svg+xml', 'image/bmp'
					];
					const fileExtension = url.split('.').pop().toLowerCase();
					const isLikelyImage = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg', 'bmp'].includes(
						fileExtension);

					if (!isLikelyImage) {
						resolve(null);
						return;
					}

					uni.getImageInfo({
						src: url,
						success: (images) => resolve(images),
						fail: (err) => {
							console.warn('获取图片信息失败，可能不是图片文件:', err);
							resolve(null);
						},
					});
				});
			},

			// 计算文件哈希
			async calculateFileHash(file) {
				try {
					return await Md5File.calculateFileHash(file, CHUNK_SIZE);
				} catch (error) {
					console.error('计算文件哈希失败:', error);
					throw error;
				}
			},

			// 上传文件主函数
			async uploadFilePromise(fileInfo, index) {
				try {
					// 检查上传状态
					const file = await postName('/oss/check-status', {
						fileSize: fileInfo.size,
						fileHash: fileInfo.fileHash,
					}, {
						custom: {
							auth: true,
							toast: true,
						},
					});
					console.log(file);
					if (file && file.exist) {
						this.toast({
							message: '文件秒传成功！'
						});
						return file;
					}

					// 根据文件大小选择上传方式
					if (fileInfo.size < CHUNK_SIZE * 2) {
						return await this.uploadFileNormal(fileInfo);
					} else {
						return await this.uploadFileMultipart(fileInfo, index);
					}
				} catch (error) {
					console.error('上传失败:', error);
					this.toast({
						message: '上传失败: ' + error.message
					});
					throw error;
				}
			},

			// 检查上传状态
			async checkUploadStatus(fileInfo) {
				try {
					return await postName('/oss/check-status', {
						fileSize: fileInfo.size,
						fileHash: fileInfo.fileHash,
					}, {
						custom: {
							auth: true,
							toast: true,
						},
					});
				} catch (error) {
					console.warn('检查上传状态失败，重新上传所有分片');
					return null;
				}
			},

			// 普通上传
			async uploadFileNormal(info) {
				const ossConfig = await this.getOSSUploadConfig(info);

				const result = await uni.uploadFile({
					url: ossConfig.host,
					filePath: info.url,
					name: 'file',
					formData: {
						key: ossConfig.key,
						policy: ossConfig.policy,
						OSSAccessKeyId: ossConfig.accessKeyId,
						signature: ossConfig.signature,
						success_action_status: '200',
					}
				});

				let statusCode, data;
				if (Array.isArray(result)) {
					const [uploadErr, uploadRes] = result;
					if (uploadErr) throw new Error(`上传失败: ${uploadErr.errMsg}`);
					statusCode = uploadRes.statusCode;
					data = uploadRes.data;
				} else {
					statusCode = result.statusCode;
					data = result.data;
				}

				if (statusCode === 200) {
					const fileUrl = `${ossConfig.host}/${ossConfig.key}`;

					await this.saveFileRecordToDB({
						storagePath: ossConfig.key,
						name: info.name,
						url: fileUrl,
						size: info.size,
						fileHash: info.fileHash,
						totalChunks: 1,
						parent: this.parentId || null,
						mimeType: info.mimeType
					});

					return {
						url: fileUrl,
						path: ossConfig.key,
						mimetype: info.type,
						baseimg: '' // 可根据需要添加base64编码
					};
				} else {
					throw new Error(`OSS上传失败，状态码: ${statusCode}`);
				}
			},

			// 分片上传
			async uploadFileMultipart(info, index) {
				let uploadId, key;

				try {
					// 初始化分片上传
					const initResult = await this.initMultipartUpload(info);
					uploadId = initResult.uploadId;
					key = initResult.key;

					const fileSize = info.size;
					const chunkCount = Math.ceil(fileSize / CHUNK_SIZE);
					const parts = [];

					// 上传所有分片
					for (let i = 0; i < chunkCount; i++) {
						const partNumber = i + 1;
						const start = i * CHUNK_SIZE;
						const end = Math.min(start + CHUNK_SIZE, fileSize);

						const partData = await this.getPartUploadUrl(uploadId, key, partNumber, info.type);
						const etag = await this.uploadPart(partData.url, info, partNumber, start, end, info.type);

						parts.push({
							partNumber,
							etag
						});

						const progress = Math.round((partNumber / chunkCount) * 99);
						this.updateUploadProgress(index, {
							progress: progress,
							uploadId: uploadId,
							key: key,
						});
					}

					// 完成分片上传
					const completeResult = await this.completeMultipartUpload(uploadId, key, parts);

					await this.saveFileRecordToDB({
						storagePath: key,
						name: info.name,
						url: completeResult.url,
						size: fileSize,
						fileHash: info.fileHash,
						totalChunks: chunkCount,
						parent: this.parentId || null,
						mimeType: info.mimeType
					});

					return {
						url: completeResult.url,
						path: key,
						mimetype: info.type,
						baseimg: ''
					};
				} catch (error) {
					// 取消分片上传
					if (uploadId && key) {
						await this.abortMultipartUpload(uploadId, key, index);
					}
					throw error;
				}
			},

			// 上传分片
			async uploadPart(url, fileInfo, partNumber, chunkStart, chunkEnd, mimeType) {
				return new Promise(async (resolve, reject) => {
					try {
						const chunkData = await this.readFileChunk(fileInfo, chunkStart, chunkEnd -
							chunkStart);

						uni.request({
							url: url,
							method: 'PUT',
							data: chunkData,
							header: {
								'Content-Type': ''
							},
							success: (res) => {
								if (res.statusCode === 200) {
									const etag = res.header.ETag || res.header.etag || res
										.header['x-oss-hash-crc64ecma'];
									const cleanEtag = etag ? etag.replace(/"/g, '') :
										`part-${partNumber}`;
									resolve(cleanEtag);
								} else {
									let errorMsg = `分片上传失败，状态码: ${res.statusCode}`;
									if (res.data) {
										try {
											const parser = new DOMParser();
											const xmlDoc = parser.parseFromString(res.data,
												"text/xml");
											const errorCode = xmlDoc.getElementsByTagName(
												"Code")[0]?.textContent;
											const errorMessage = xmlDoc.getElementsByTagName(
												"Message")[0]?.textContent;
											if (errorCode) {
												errorMsg +=
													`, 错误码: ${errorCode}, 信息: ${errorMessage}`;
											}
										} catch (e) {
											errorMsg += `, 响应: ${res.data}`;
										}
									}
									reject(new Error(errorMsg));
								}
							},
							fail: (err) => {
								reject(new Error(`分片上传请求失败: ${err.errMsg}`));
							}
						});
					} catch (error) {
						reject(new Error(`读取文件分片失败: ${error.message}`));
					}
				});
			},

			// 读取文件分片
			readFileChunk(fileInfo, start, length) {
				return new Promise((resolve, reject) => {
					// #ifdef H5
					this.readChunkH5(fileInfo, start, length).then(resolve).catch(reject);
					// #endif

					// #ifdef MP-WEIXIN
					this.readChunkWeixin(fileInfo, start, length).then(resolve).catch(reject);
					// #endif

					// #ifndef H5 || MP-WEIXIN
					this.readChunkUni(fileInfo, start, length).then(resolve).catch(reject);
					// #endif
				});
			},

			// H5环境读取分片
			readChunkH5(fileInfo, start, length) {
				return new Promise((resolve, reject) => {
					fetch(fileInfo.url)
						.then(response => response.blob())
						.then(blob => {
							const chunkBlob = blob.slice(start, start + length);
							const reader = new FileReader();
							reader.onload = () => resolve(reader.result);
							reader.onerror = reject;
							reader.readAsArrayBuffer(chunkBlob);
						})
						.catch(reject);
				});
			},

			// 微信小程序环境读取分片
			readChunkWeixin(fileInfo, start, length) {
				return new Promise((resolve, reject) => {
					const fileSystemManager = uni.getFileSystemManager();
					fileSystemManager.readFile({
						filePath: fileInfo.url,
						position: start,
						length: length,
						success: (res) => resolve(res.data),
						fail: (err) => reject(err)
					});
				});
			},

			// 通用uni.readFile读取分片
			readChunkUni(fileInfo, start, length) {
				return new Promise((resolve, reject) => {
					uni.readFile({
						filePath: fileInfo.url,
						success: (res) => {
							const arrayBuffer = res.data;
							const chunk = arrayBuffer.slice(start, start + length);
							resolve(chunk);
						},
						fail: (err) => reject(err)
					});
				});
			},

			// OSS相关方法
			async initMultipartUpload(info) {
				return await postName('/oss/initMultipartUpload', {
					filename: info.name,
					mimeType: info.type,
					size: info.size,
					fileHash: info.fileHash,
				}, {
					custom: {
						auth: true,
						toast: true,
						catch: true
					},
				});
			},

			async getPartUploadUrl(uploadId, key, partNumber, mimeType) {
				return await postName('/oss/getPartUploadUrl', {
					uploadId,
					key,
					partNumber,
					mimeType
				}, {
					custom: {
						auth: true,
						toast: false,
						catch: true
					},
				});
			},

			async completeMultipartUpload(uploadId, key, parts) {
				return await postName('/oss/completeMultipartUpload', {
					uploadId,
					key,
					parts
				}, {
					custom: {
						auth: true,
						toast: false,
						catch: true
					},
				});
			},

			async abortMultipartUpload(uploadId, key, index) {
				try {
					await postName('/oss/abortMultipartUpload', {
						uploadId,
						key
					}, {
						custom: {
							auth: true,
							toast: false,
							catch: false
						},
					});
					this.updateUploadProgress(index, {
						progress: 0
					});
				} catch (error) {
					console.warn('取消分片上传失败:', error);
				}
			},

			async saveFileRecordToDB(fileInfo) {
				const result = await postName('/oss/saveFileRecord', fileInfo, {
					custom: {
						auth: true,
						toast: true
					},
				});
				this.toast({
					message: '文件上传成功！'
				});
				return result;
			},
			async getFileMimeType(info) {
				// 如果文件已经有type属性，直接使用
				if (info.type.includes('/')) {
					return info.type;
				}

				// 根据文件扩展名推断MIME类型
				const getExtension = (filename) => {
					return filename.split('.').pop().toLowerCase();
				};

				const ext = getExtension(info.name || info.path || '');
				const mimeTypes = {
					// 图像格式
					'jpg': 'image/jpeg',
					'jpeg': 'image/jpeg',
					'png': 'image/png',
					'gif': 'image/gif',
					'bmp': 'image/bmp',
					'webp': 'image/webp',

					// 视频格式
					'mp4': 'video/mp4',
					'avi': 'video/x-msvideo',
					'mov': 'video/quicktime',
					'wmv': 'video/x-ms-wmv',
					'flv': 'video/x-flv',
					'webm': 'video/webm',
					'mkv': 'video/x-matroska',

					// 文档格式
					'pdf': 'application/pdf',
					'doc': 'application/msword',
					'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
					'xls': 'application/vnd.ms-excel',
					'xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
					'ppt': 'application/vnd.ms-powerpoint',
					'pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',

					// 压缩文件
					'zip': 'application/zip',
					'rar': 'application/x-rar-compressed',

					// 文本格式
					'txt': 'text/plain',
					'json': 'application/json',
					'xml': 'application/xml'
				}

				return mimeTypes[ext] || 'application/octet-stream';
			},


			async getOSSUploadConfig(info) {
				return await getName(
					`oss/getOSSToken?filename=${info.name}&fileHash=${info.fileHash}&size=${info.size}`, {
						custom: {
							auth: true,
							toast: false,
							catch: true
						}
					});
			},

			// 更新上传进度
			updateUploadProgress(i, progress) {
				this.$emit('upload-progress', i, progress);
			},

			// 更新文件列表
			updateFileList(index, data) {
				let item = this.fileList[index];
				this.fileList.splice(index, 1, {
					...item,
					status: 'success',
					message: '',
					url: data.url,
					path: data.path,
					base64: `data:${data.mimetype};base64,${data.baseimg}`,
					baseimg: data.baseimg
				});

				let ites = this.resdata[index] || {};
				this.resdata.splice(index, 1, {
					...ites,
					url: data.url,
					path: data.path,
					base64: `data:${data.mimetype};base64,${data.baseimg}`,
					baseimg: data.baseimg
				});
			},
		}
	}
</script>

<style scoped lang="scss">
	.uploadFile {
		margin: 0px;
		display: flex;
		justify-content: center;
		align-items: center;
		width: 100%;

		.uv_upload {
			width: 100%;
			border: 1px;
			border-color: #ffaaff;
			border-style: solid;
			overflow: hidden;

			::v-deep .uv-upload__wrap__preview {
				margin: 0px;
			}

			::v-deep .uv-upload__button {
				margin: 0px;
			}

			::v-deep .uv-upload__deletable {
				height: 25px;
				width: 25px;
				cursor: pointer;
			}

			::v-deep .uv-upload__deletable__icon {
				top: 5px;
				right: 5px
			}

			::v-deep .uv-upload__success {
				bottom: 0px;
				right: 2px;
			}

			::v-deep .uv-upload__deletable {
				z-index: 0;
			}
		}
	}

	.uploadFile_max {
		display: flex;
		justify-content: center;
		align-items: center;

		.uv_upload {
			border: 0px;
			margin-bottom: 5px;

			::v-deep .uv-upload__wrap {
				margin-right: 10px;
				display: flex;
				flex-direction: row;
				flex-wrap: wrap;
				flex: 1;
			}

			::v-deep .uv-upload__wrap__preview {
				margin-right: 10px;
				border-radius: 6px;
				margin-bottom: 6px;
			}
		}
	}
</style>